#include"tcp_client.h"

#include<string.h>
#include<unistd.h>
#include<errno.h>


// ============== 创建链接/销毁链接 Hook 部分 ====
//设置链接的创建hook函数
void tcp_client::set_conn_start(conn_callback cb, void *args){
    conn_start_cb = cb;
    conn_start_cb_args = args;
}

//设置链接的销毁hook函数
void tcp_client::set_conn_close(conn_callback cb, void *args){
    conn_close_cb = cb;
	conn_close_cb_args = args;
}



// ====初始化路由分发机制句柄=============

void tcp_client::add_msg_router(int msgid, msg_callback *msg_cb, void* args){
	_router.register_msg_router(msgid, msg_cb, args);
}


// 读回调函数
void read_callback(event_loop* loop, int fd, void* args){
    tcp_client* client = (tcp_client*)args;
    client->do_read();
}

// 写回调函数
void write_callback(event_loop* loop, int fd, void* args){

    tcp_client* client = (tcp_client*)args;
    client->do_write();

}


// 判断是否链接成功的回调函数 
void connection_success(event_loop* loop, int fd, void* args){
    
    tcp_client *client = (tcp_client*)args;
    loop->del_io_event(fd);

    // 在对fd进行错误编码的获取，如果没有那就证明连接成功了
    // 如果有，则认为fd没有连接成功
    int result = 0;
    socklen_t result_len = sizeof(result);
    getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &result_len);
    if (result == 0) {
        // 表示当前链接成功
        printf("connect %s:%d succ!\n", inet_ntoa(client->_server_addr.sin_addr), ntohs(client->_server_addr.sin_port));
        
        // 针对当前的fd添加读回调
        loop->add_io_event(fd, read_callback, EPOLLIN, client);

        //调用开发者注册的创建链接Hook函数
        if (client->conn_start_cb != NULL) {
            client->conn_start_cb(client, client->conn_start_cb_args);
        }
        
        // 如果创建连接成功，

        // // 客户端主动发一个消息给server
        // const char* data = "hello lars, I am Client\n";
        // int msgid = 2;
        // client->send_message(data, strlen(data), msgid);

    }   
    else{
         //链接创建失败
        fprintf(stderr, "connection %s:%d error\n", inet_ntoa(client->_server_addr.sin_addr), ntohs(client->_server_addr.sin_port));
        return ;
    }

    // 链接创建成功后的的业务
}

tcp_client::tcp_client(event_loop* loop, const char* ip, uint16_t port){
    _sockfd = -1;
    _loop = loop;

    conn_start_cb = NULL;
    conn_start_cb_args = NULL;
    conn_close_cb = NULL;
    conn_close_cb_args = NULL;

    // 绑定server地址
    bzero(&_server_addr,sizeof(_server_addr));
    _server_addr.sin_family = AF_INET;
    inet_aton(ip, &_server_addr.sin_addr);
    _server_addr.sin_port = htons(port);

    _addrlen = sizeof(_server_addr);

    // 连接远程客户端
    this->do_connect();
}

// 发送消息方法
int tcp_client::send_message(const char* data, int msglen, int msgid){
    //如果现在已经数据都发送完了，那么是需要重新激活写事件
    //如果有数据，说明数据还没有完全写完，那么没必要再重复激活 
    bool active_epollout = false; 
    if(obuf.length() == 0) {
        active_epollout = true;
    }
    // 1.封装一个message包头
    msg_head head;
    head.msgid = msgid;
    head.msglen = msglen;
    // 将消息头写入obuf缓冲区
    int ret = obuf.send_data((const char *)&head, MESSAGE_HEAD_LEN);
    if(ret!=0){
        fprintf(stderr,"send head error\n");
        return -1;
    }
    // 2.写消息体
    ret = obuf.send_data(data,msglen);
    if(ret != 0){
        // 如果消息体写失败，则消息头需要弹出
        fprintf(stderr,"send data error\n");
        obuf.pop(MESSAGE_HEAD_LEN);
        return -1;
    }

    printf("tcp_client::send_message:client send id:%d len:%d\n",msgid, msglen);
    // 3.将_sockfd添加一个写EPOLLOUT事件，回调让obuf中的数据写回给对端客户端
    if (active_epollout == true) {
        _loop->add_io_event(_sockfd, write_callback, EPOLLOUT, this);
    }
    return 0;
}

// 链接服务器
void tcp_client::do_connect(){
    // 如果上次连接并未关闭
    if(_sockfd != -1){
        close(_sockfd);
    }

    // 创建套接字
    _sockfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP);
	if(_sockfd==-1){
		fprintf(stderr,"tcp_client:socker error!\n");
		exit(1);
	}

    // 创建连接
    int ret = connect(_sockfd, (const struct sockaddr*)&_server_addr, _addrlen);
    if (ret == 0) {
        // 说明创建连接成功
        
        //调用开发者客户端注册的创建链接之后的hook函数
        if (conn_start_cb != NULL) {
            conn_start_cb(this, conn_start_cb_args);
        }

        connection_success(_loop, _sockfd, this);
        
    }
    else{
        if(errno == EINPROGRESS) {
            //fd是非阻塞的，可能会出现这个错误,但是并不表示链接创建失败
            //如果fd是可写状态，则为链接是创建成功的.
            fprintf(stderr, "do_connect EINPROGRESS\n");

            //让event_loop去触发一个创建判断链接业务 用EPOLLOUT事件立刻触发
            _loop->add_io_event(_sockfd, connection_success, EPOLLOUT, this);
        }
        else {
            fprintf(stderr, "connection error\n");
            exit(1);
        }
    }
}

// 处理读事件
void tcp_client::do_read(){
    
    // 1.从_sockfd中读取数据
    int ret = ibuf.read_data(_sockfd);
	if(ret == -1){
		fprintf(stderr,"ibuf read_data error\n");
		this->clean_conn();
		return ;
	}
	else if(ret == 0){
		//对方正常关闭，删除当前读事件
        printf("peer server closed!\n");
        this->clean_conn();
		return ;
	} 

    msg_head head;
    // 2.判断读取过来的数据是否满足8字节
    while(ibuf.length() >= MESSAGE_HEAD_LEN){
        // 2.1 先读头部信息，得到msgid和msglen
        memcpy(&head, ibuf.data(), MESSAGE_HEAD_LEN);
        if(head.msglen>MESSAGE_LENGTH_LIMIT || head.msglen<0){
            fprintf(stderr,"message format error too large or too small\n");
            this->clean_conn();
		    break;
        }

        // 2.2判断客户端是否读完
        if(ibuf.length()<MESSAGE_HEAD_LEN + head.msglen){
            // ibuf中缓存的数据并不是完整的数据包
            break;
        }
        
        // 2.3 当前ibuf缓存中的数据是完整的客户端数据
        ibuf.pop(MESSAGE_HEAD_LEN);
    
        // 处理数据
        // printf("tcp_client::do_read:recv data from server = %s\n", ibuf.data());

        // 针对不同的msgid调用不同的业务回调函数
        _router.call(head.msgid, ibuf.data(), head.msglen, this);

        //消息体处理完了,去掉msglen长度
        ibuf.pop(head.msglen);

    }

    ibuf.adjust();
    return ;
}

// 处理写事件
void tcp_client::do_write(){
    //do_write表示obuf缓冲中已经有要写的数据了，就应该直接调用write2fd写出数据

    //只要obuf中有数据就写
    while(obuf.length()){
        int ret = obuf.write2fd(_sockfd);
        if(ret == -1){
            fprintf(stderr,"tcp_client obuf write2fd error\n");
            this->clean_conn();
            return ;
        }
        else if(ret == 0){
            // 当前不可写
            fprintf(stderr, "obuf write2fd error\n");
            break;
        }
    }

    //数据已经全部写完，将_sockfd的写事件取消掉
    if (obuf.length() == 0) {
        _loop->del_io_event(_sockfd, EPOLLOUT);
    }

    return ;
}

// 释放链接
void tcp_client::clean_conn(){
    //调用开发者注册的销毁链接之前触发的Hook
    if (conn_close_cb != NULL) {
        conn_close_cb(this, conn_close_cb_args);
    }
    
    if(_sockfd != -1){

        _loop->del_io_event(_sockfd);
        close(_sockfd);
        ibuf.clear();
        obuf.clear();
    }

}

tcp_client::~tcp_client(){

}